#include "include/AppLog.h"
#include <jni.h>
#include <EGL/egl.h>
//#include <EGL/eglext.h>
//#include <KHR/khrplatform.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <gl.h>
#include <egl_core.h>
#include <opengl.h>
#include <pthread.h>
#include <buffer_pool.h>
#include <message_queue.h>
#include <frame_buffer.h>
#include <tools.h>


#ifdef __cplusplus
extern "C" {
#endif


#ifndef NELEM
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif

#define RECORD_NAME "me/payge/video/VideoRecorder"

using namespace av_sdk;

static int camera_width_, camera_height_;
static ANativeWindow *window_;
static EGLCore *eglCore_;
static EGLSurface preview_surface_;

static int frame_type_ = 2;
static FrameBuffer* frame_buffer_;
static OpenGL *render_screen_;

static GLuint oes_texture_id_;

//static pthread_mutex_t lock_;
//static pthread_cond_t condition_;
static pthread_t pthread_;

static BufferPool *message_pool_;
static MessageQueue *message_queue_;

static jobject recorderObject_;
static JavaVM *vm_;

static GLfloat* texture_matrix_;



static void* ThreadStartCallback(void *ptr) {
    LOGD("enter %s", __func__);
    bool isRenderEnabled = true;
    while (isRenderEnabled) {
        Message *msg = nullptr;
        if (message_queue_->DequeueMessage(&msg, true) > 0) {
            if (nullptr != msg) {
                if (MESSAGE_QUEUE_LOOP_QUIT_FLAG == msg->Execute()) {
                    LOGE("%s MESSAGE_QUEUE_LOOP_QUIT_FLAG", __func__);
                    isRenderEnabled = false;
                }
                if (nullptr != message_pool_) {
                    message_pool_->ReleaseBuffer(msg);
                }
            }
        }
    }
    LOGD("exit %s", __func__);
    pthread_exit(0);
}


static void UpdateTexImage() {
    JNIEnv *env;
    if (vm_->AttachCurrentThread(&env, nullptr) != JNI_OK) {
        LOGE("%s: AttachCurrentThread() failed", __func__);
        return;
    }
    jclass clazz = env->GetObjectClass(recorderObject_);
    if (nullptr != clazz) {
        jmethodID updateTexImageCallback = env->GetMethodID(clazz, "updateTexImageFromNative", "()V");
        if (nullptr != updateTexImageCallback) {
            env->CallVoidMethod(recorderObject_, updateTexImageCallback);
        }
    }
    if (vm_->DetachCurrentThread() != JNI_OK) {
        LOGE("%s: DetachCurrentThread() failed", __func__);
    }
}


static void GetTextureMatrix() {
    JNIEnv *env;
    if (vm_->AttachCurrentThread(&env, nullptr) != JNI_OK) {
        LOGE("%s: AttachCurrentThread() failed", __func__);
        return;
    }
    jclass clazz = env->GetObjectClass(recorderObject_);
    if (nullptr != clazz) {
        jmethodID getTextureMatrix = env->GetMethodID(clazz, "getTextureMatrix", "()[F");
        if (nullptr != getTextureMatrix) {
            auto matrix = reinterpret_cast<jfloatArray>(env->CallObjectMethod(recorderObject_, getTextureMatrix));
            float* texture_matrix = env->GetFloatArrayElements(matrix, JNI_FALSE);
            memcpy(texture_matrix_, texture_matrix, sizeof(float) * 16);
            env->ReleaseFloatArrayElements(matrix, texture_matrix, 0);
        }
    }
    if (vm_->DetachCurrentThread() != JNI_OK) {
        LOGE("%s: DetachCurrentThread() failed", __func__);
    }
}


static bool Draw(Message *msg) {
    LOGD("%s", __func__);
    eglCore_->MakeCurrent(preview_surface_);
    UpdateTexImage();
    GetTextureMatrix();
    if (frame_buffer_ == nullptr) {
        frame_buffer_ = new FrameBuffer(MIN(camera_width_, camera_height_),
                                        MAX(camera_width_, camera_height_),
                                        DEFAULT_VERTEX_MATRIX_SHADER, DEFAULT_OES_FRAGMENT_SHADER);
        enum RenderFrame frame_type = FIT;
        if (frame_type_ == 0) {
            frame_type = SQUARE;
        } else if (frame_type_ == 1) {
            frame_type = FIT;
        } else if (frame_type_ == 2) {
            frame_type = CROP;
        }
        render_screen_->SetFrame(MIN(camera_width_, camera_height_),
                                 MAX(camera_width_, camera_height_),
                                 camera_width_, camera_height_, frame_type);
    }
    render_screen_->SetOutput(camera_width_, camera_height_);
    frame_buffer_->SetOutput(MIN(camera_width_, camera_height_),
                             MAX(camera_width_, camera_height_));
    frame_buffer_->SetTextureType(TEXTURE_OES);
    frame_buffer_->ActiveProgram();
    // 把oes转成普通的TEXTURE_2D类型, 并且根据SurfaceTexture的matrix矩阵进行旋转
    GLuint texture_id = frame_buffer_->OnDrawFrame(oes_texture_id_, texture_matrix_);
    render_screen_->ActiveProgram();
    render_screen_->ProcessImage(texture_id);

    if (!eglCore_->SwapBuffers(preview_surface_)) {
        LOGE("eglSwapBuffers(preview_surface_) returned error %d", eglGetError());
    }
    return true;
}


static GLuint InitOESTextureId() {
    glGenTextures(1, &oes_texture_id_);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, oes_texture_id_);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
    return oes_texture_id_;
}


static void StartPreview() {
    JNIEnv *env;
    if (vm_->AttachCurrentThread(&env, nullptr) != JNI_OK) {
        LOGE("%s: AttachCurrentThread() failed", __func__);
        return;
    }
    jclass clazz = env->GetObjectClass(recorderObject_);
    if (nullptr != clazz) {
        jmethodID start_preview_callback = env->GetMethodID(clazz, "startPreviewFromNative", "(I)V");
        if (nullptr != start_preview_callback) {
            jint oes_texture_id = oes_texture_id_;
            env->CallVoidMethod(recorderObject_, start_preview_callback, oes_texture_id);
        }
    }
    if (vm_->DetachCurrentThread() != JNI_OK) {
        LOGE("%s: DetachCurrentThread() failed", __func__);
    }
}


static bool CreateWindowSurface(Message *msg) {
    texture_matrix_ = new GLfloat[16];
    eglCore_ = new EGLCore();
    eglCore_->Init();
    preview_surface_ = eglCore_->CreateWindowSurface(window_);
    eglCore_->MakeCurrent(preview_surface_);
    InitOESTextureId();
    StartPreview();
    render_screen_ = new OpenGL(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER);
    return true;
}


static void DestroySurface() {
    LOGI("enter %s", __func__);
    if (EGL_NO_SURFACE != preview_surface_) {
        //onSurfaceDestroy();
        eglCore_->ReleaseSurface(preview_surface_);
        preview_surface_ = EGL_NO_SURFACE;
        if (nullptr != window_) {
            ANativeWindow_release(window_);
            window_ = nullptr;
        }
    }
    LOGI("leave %s", __func__);
}


static void ReleaseCamera() {
    LOGI("enter %s", __func__);
    JNIEnv* env;
    if (vm_->AttachCurrentThread(&env, nullptr) != JNI_OK) {
        LOGE("AttachCurrentThread failed.");
        return;
    }
    if (nullptr == env)  {
        LOGE("JNIEnv init failed.");
        return;
    }
    jclass clazz = env->GetObjectClass(recorderObject_);
    if (nullptr != clazz) {
        jmethodID releaseCameraCallback = env->GetMethodID(clazz, "releaseCameraFromNative", "()V");
        if (nullptr != releaseCameraCallback) {
            env->CallVoidMethod(recorderObject_, releaseCameraCallback);
        }
    }
    if (vm_->DetachCurrentThread() != JNI_OK) {
        LOGE("%s: DetachCurrentThread() failed", __func__);
        return;
    }
    LOGI("leave %s", __func__);
}


static bool Release(Message *msg) {
    LOGI("enter %s", __func__);
    if (oes_texture_id_ != 0) {
        glDeleteTextures(1, &oes_texture_id_);
        oes_texture_id_ = 0;
    }
    if (nullptr != frame_buffer_) {
        delete frame_buffer_;
        frame_buffer_ = nullptr;
    }
    if (nullptr != render_screen_) {
        delete render_screen_;
        render_screen_ = nullptr;
    }

    DestroySurface();
    ReleaseCamera();

    if (nullptr != eglCore_) {
        LOGI("destroy context: %p", eglCore_->GetContext());
        eglCore_->Release();
        delete eglCore_;
        eglCore_ = nullptr;
    }
    if (nullptr != texture_matrix_) {
        delete[] texture_matrix_;
        texture_matrix_ = nullptr;
    }
    LOGI("leave %s", __func__);
    return true;
}


static void JNI_createSurface(JNIEnv *env, jobject object, jobject surface, jint width, jint height) {
    LOGD("%s", __func__);
    env->GetJavaVM(&vm_);
    recorderObject_ = env->NewGlobalRef(object);
    camera_width_ = width;
    camera_height_ = height;
    window_ = ANativeWindow_fromSurface(env, surface);
    message_pool_ = new BufferPool(sizeof(Message));
    message_queue_ = new MessageQueue("record message queue");
    Message *msg = message_pool_->GetBuffer<Message>();
    msg->executeAction = CreateWindowSurface;
    message_queue_->EnqueueMessage(msg);
    pthread_create(&pthread_, nullptr, ThreadStartCallback, nullptr);
}


static void JNI_onFrameAvailable(JNIEnv *env, jobject object) {
    Message *msg = message_pool_->GetBuffer<Message>();
    msg->executeAction = Draw;
    message_queue_->EnqueueMessage(msg);
}


static void JNI_release(JNIEnv *env, jobject object) {
    LOGI("enter %s", __func__);
    Message *msg = message_pool_->GetBuffer<Message>();
    msg->executeAction = Release;
    message_queue_->EnqueueMessage(msg);
    msg = message_pool_->GetBuffer<Message>();
    msg->what = MESSAGE_QUEUE_LOOP_QUIT_FLAG;
    message_queue_->EnqueueMessage(msg);
    pthread_join(pthread_, 0);
    message_queue_->Abort();
    delete message_queue_;
    message_queue_ = nullptr;
    env->DeleteGlobalRef(recorderObject_);
    recorderObject_ = nullptr;
    LOGI("leave %s", __func__);
}


static JNINativeMethod recordMethods[] = {
    {"create", "(Landroid/view/Surface;II)V", (void **) JNI_createSurface},
    {"onFrameAvailable", "()V", (void **) JNI_onFrameAvailable},
    {"release", "()V", (void **) JNI_release}
};


JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    if ((vm)->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    jclass record = env->FindClass(RECORD_NAME);
    env->RegisterNatives(record, recordMethods, NELEM(recordMethods));
    env->DeleteLocalRef(record);
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) {

}

#ifdef __cplusplus
}
#endif